﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace Dycox.Linq
{
    public enum TokenType
    {
        Literal,
        BinaryOperator,
        UnaryOperator,
        OpenBracket,
        CloseBracket,
    }

    public abstract class Token
    {
        protected Token(TokenType type)
        {
            _type = type;
        }

        private TokenType _type;

        public TokenType Type
        {
            get { return _type; }
        }

    }

    public abstract class OperatorToken : Token
    {
        static readonly Dictionary<string, ExpressionType> BinaryOperators;
        static readonly Dictionary<string, ExpressionType> UnaryOperators;

        static OperatorToken()
        {
            BinaryOperators = new Dictionary<string, ExpressionType>();
            BinaryOperators["=="] = ExpressionType.Equal;
            BinaryOperators["="] = ExpressionType.Equal;
            BinaryOperators["!="] = ExpressionType.NotEqual;
            BinaryOperators["<>"] = ExpressionType.NotEqual;
            BinaryOperators["&"] = ExpressionType.AndAlso;
            BinaryOperators["&&"] = ExpressionType.AndAlso;
            BinaryOperators["|"] = ExpressionType.OrElse;
            BinaryOperators["||"] = ExpressionType.OrElse;
            BinaryOperators[">"] = ExpressionType.GreaterThan;
            BinaryOperators[">="] = ExpressionType.GreaterThanOrEqual;
            BinaryOperators["<"] = ExpressionType.LessThan;
            BinaryOperators["<="] = ExpressionType.LessThanOrEqual;

            UnaryOperators = new Dictionary<string, ExpressionType>();
            UnaryOperators["!"] = ExpressionType.Not;
        }


        protected OperatorToken(ExpressionType exprType, TokenType tokenType)
            : base(tokenType)
        {
            _expressionType = exprType;
        }

        private ExpressionType _expressionType;

        public ExpressionType ExpressionType
        {
            get { return _expressionType; }
        }

        public abstract Expression CreateExpression(ParameterExpression param, params ExpressionToken[] tokens);

        public static OperatorToken CreateToken(string text)
        {
            ExpressionType exprType;

            if (BinaryOperators.TryGetValue(text, out exprType))
                return new BinaryOperatorToken(exprType);
            else if (UnaryOperators.TryGetValue(text, out exprType))
                return new UnaryOperatorToken(exprType);
            else
                throw new SyntaxException(text + " is not a valid operator.");
        }

        public static bool IsOperatorSupported(string text)
        {
            return BinaryOperators.ContainsKey(text) || UnaryOperators.ContainsKey(text);
        }

        public override string ToString()
        {
            return "Operator: " + _expressionType.ToString();
        }
    }

    public sealed class UnaryOperatorToken : OperatorToken
    {
        internal UnaryOperatorToken(ExpressionType exprType)
            : base(exprType, TokenType.UnaryOperator)
        {

        }

        public override Expression CreateExpression(ParameterExpression param, params ExpressionToken[] tokens)
        {
            if (tokens.Length == 0)
                throw new ArgumentException();

            return Expression.MakeUnary(this.ExpressionType, tokens[0].CreateExpression(param), null);
        }
    }

    public sealed class BinaryOperatorToken : OperatorToken
    {
        internal BinaryOperatorToken(ExpressionType exprType)
            : base(exprType, TokenType.BinaryOperator)
        {

        }

        public override Expression CreateExpression(ParameterExpression param, params ExpressionToken[] tokens)
        {
            if (tokens.Length < 2)
                throw new ArgumentException();

            return Expression.MakeBinary(this.ExpressionType,
                tokens[0].CreateExpression(param),
                tokens[1].CreateExpression(param));
        }
    }

    public abstract class LiteralToken : ExpressionToken
    {
        public LiteralToken(string text)
        {
            if (text == null)
                throw new ArgumentNullException("text");

            _text = text;
        }

        private string _text;

        public string Text
        {
            get { return _text; }
        }

        public override string ToString()
        {
            return Type.ToString() + ": " + _text;
        }

    }

    public sealed class BracketToken : Token
    {
        public BracketToken(bool isCloseBracket)
            : base(isCloseBracket ? TokenType.CloseBracket : TokenType.OpenBracket)
        {

        }

        public override string ToString()
        {
            return Type.ToString();
        }
    }

    internal sealed class RawExpressionToken : ExpressionToken
    {
        public RawExpressionToken(Expression expr)
        {
            _expression = expr;
        }

        private Expression _expression;

        public Expression Expression
        {
            get { return _expression; }
        }

        public override Expression CreateExpression(ParameterExpression param)
        {
            return _expression;
        }
    }

    public abstract class ExpressionToken : Token
    {
        public ExpressionToken()
            : base(TokenType.Literal)
        {

        }

        public abstract Expression CreateExpression(ParameterExpression param);

    }
}
